package com.google.gwt.user.dragdrop.client;

/*
 * Copyright 2009 Gal Dolber
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.UserAgent;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HTMLTable.RowFormatter;
import com.google.gwt.user.client.ui.UserAgent.Agents;
import com.google.gwt.user.dragdrop.client.impl.ResizeImpl;
import com.google.gwt.user.dragdrop.client.resources.Theme;
import com.google.gwt.user.dragdrop.client.util.ResizeHandler;

/**
 * This is a composite controller that adds resize capabilities to any widget
 */
public class ResizeController extends Composite implements DelegateDragHandler,
		HasDragHandlers, DelegateResizeHandler {

	/**
	 * Make the widget resizable
	 * 
	 * @param widget
	 * @return
	 */
	public static ResizeController make(Widget widget) {
		return make(widget, new ResizeImpl());
	}

	/**
	 * Make the widget resizable with custom Draggable implementation
	 * 
	 * @param widget
	 * @return
	 */
	public static ResizeController make(Widget widget,
			ResizeHandler resizeHandler) {
		return new ResizeController(widget, resizeHandler);
	}

	protected int deltaX;

	protected int deltaY;

	protected Grid grid = new Grid(3, 3);
	protected CellFormatter cellFormatter = grid.getCellFormatter();
	protected RowFormatter rowFormatter = grid.getRowFormatter();

	protected HasResizeHandlers impl;

	private int initHeight;

	private int initWidth;

	private int initX;

	private int initY;

	private AbsolutePanel p;

	private ResizeBorders currentTargetBorder;

	private DragController dragController;

	private boolean bordersVisible = false;

	public ResizeController() {
		this(Theme.BLUE);

		this.impl = new ResizeImpl();
	}

	public ResizeController(HasResizeHandlers impl) {
		this(Theme.BLUE);

		this.impl = impl;
	}

	/**
	 * Creates a new panel using the specified style names to apply to each row.
	 * Each row will contain three cells (Left, Center, and Right). The Center
	 * cell in the containerIndex row will contain the {@link Widget}.
	 * 
	 * @param rowStyles
	 *            an array of style names to apply to each row
	 * @param containerIndex
	 *            the index of the container row
	 */
	public ResizeController(Theme theme) {
		grid.setCellPadding(0);
		grid.setCellSpacing(0);

		initWidget(grid);

		String[] rowStyles = { "Dtop", "Dmiddle", "Dbottom" };
		String[] colStyles = { "Left", "Center", "Right" };

		// Add each row
		for (int x = 0; x < 3; x++) {
			for (int y = 0; y < 3; y++) {
				String cellStyle = rowStyles[y] + colStyles[x];

				Element e = cellFormatter.getElement(y, x);
				e.setClassName(cellStyle);
			}
		}

		for (int i = 0; i < 3; i++) {
			rowFormatter.getElement(i).setClassName(rowStyles[i]);
		}

		// Safari line-height problem
		if (UserAgent.getUserAgent() == Agents.SAFARI) {
			rowFormatter.getElement(0).getStyle().setProperty("lineHeight",
					"0px");
			rowFormatter.getElement(2).getStyle().setProperty("lineHeight",
					"0px");
		}

		setStylePrimaryName(theme.toString());

		addStyleName("resizeEnable");

		dragController = DragController.make(grid, this);
	}

	public ResizeController(Widget w, final HasResizeHandlers impl) {
		this(Theme.BLACK);

		this.impl = impl;

		setWidget(w);
	}

	public void disableResizing() {
		dragController.disableDrag();
		removeStyleName("resizeEnable");
	}

	public void enableResizing() {
		dragController.enableDrag();
		addStyleName("resizeEnable");
	}

	public int getCurrentHeight() {
		return getResizeDelegate().getOffsetHeight();
	}

	public int getCurrentWidth() {
		return getResizeDelegate().getOffsetWidth();
	}

	@Override
	public Widget getDragDelegate() {
		Widget handler;
		try {
			handler = ((DelegateDragHandler) getWidget()).getDragDelegate();
		} catch (Exception e) {
			handler = getWidget();
		}
		return handler;
	}

	public int getFixedX(int dx) {
		int newx = initX + dx;
		if (newx > initWidth + initX) {
			newx = initWidth + initX;
		}
		return newx;
	}

	public int getFixedY(int deltaY) {
		int newy = initY + deltaY;
		if (newy > initHeight + initY) {
			newy = initHeight + initY;
		}
		return newy;
	}

	public int getHeight() {
		return getWidget().getOffsetHeight();
	}

	public int getInitHeight() {
		return initHeight;
	}

	public int getInitWidth() {
		return initWidth;
	}

	public int getInitX() {
		return initX;
	}

	public int getInitY() {
		return initY;
	}

	/**
	 * @return Widgets left
	 */
	public int getLeft() {
		return p.getWidgetLeft(this);
	}

	@Override
	public Widget getResizeDelegate() {
		return getWidget();
	}

	public ResizeBorders getTargetBorder() {
		return currentTargetBorder;
	}

	/**
	 * @return Widgets top
	 */
	public int getTop() {
		return p.getWidgetTop(this);
	}

	@Override
	protected Widget getWidget() {
		return grid.getWidget(1, 1);
	}

	public int getWidth() {
		return getWidget().getOffsetWidth();
	}

	/**
	 * Disable resize borders
	 */
	public void hideBorders() {
		showBorders(false);
	}

	protected void initMoving() {
		p = (AbsolutePanel) getParent();
		initX = p.getWidgetLeft(ResizeController.this);
		initY = p.getWidgetTop(ResizeController.this);
		initWidth = getResizeDelegate().getOffsetWidth();
		initHeight = getResizeDelegate().getOffsetHeight();
	}

	/**
	 * Add to the position
	 * 
	 * @param left
	 * @param top
	 */
	public void movePosition(int left, int top) {
		p.setWidgetPosition(ResizeController.this,
				p.getWidgetLeft(this) + left, p.getWidgetTop(this) + top);
	}

	@Override
	public void onDragEnd(DragController controller) {
		if (currentTargetBorder != ResizeBorders.INVALID
				&& currentTargetBorder != ResizeBorders.CENTERCENTER) {
			controller.stopEventPropagation();
			DOM.releaseCapture(controller.getHandler().getElement());
			impl.onResizeEnd(ResizeController.this, controller);
		}
	}

	@Override
	public void onDragMove(DragController controller) {
		if (currentTargetBorder != ResizeBorders.INVALID
				&& currentTargetBorder != ResizeBorders.CENTERCENTER) {
			controller.stopEventPropagation();
			impl.onResize(ResizeController.this, controller);
		}
	}

	@Override
	public void onDragStart(DragController controller) {
		initMoving();

		saveEventTargetBorder(controller);

		if (currentTargetBorder != ResizeBorders.INVALID
				&& currentTargetBorder != ResizeBorders.CENTERCENTER) {
			controller.stopEventPropagation();
			DOM.setCapture(controller.getHandler().getElement());
			impl.onResizeStart(ResizeController.this, controller);
		}
	}

	/**
	 * Saves the current parent
	 * 
	 * @param parent
	 */
	public void saveContainer() {
		p = (AbsolutePanel) getParent();
	}

	private void saveEventTargetBorder(DragController controller) {
		Element target = controller.getCurrentEventTarget();

		for (int x = 0; x < 3; x++) {
			for (int y = 0; y < 3; y++) {
				if (cellFormatter.getElement(y, x).equals(target)) {
					currentTargetBorder = ResizeBorders.values()[x * 3 + y];
					return;
				}
			}
		}

		currentTargetBorder = ResizeBorders.INVALID;
	}

	/**
	 * Sets the implementation
	 * 
	 * @param impl
	 */
	public void setImpl(HasResizeHandlers impl) {
		this.impl = impl;
	}

	/**
	 * Sets the position
	 * 
	 * @param left
	 * @param top
	 */
	public void setPosition(int left, int top) {
		p.setWidgetPosition(this, left, top);
	}

	/**
	 * Set the size of the widget and the borders
	 * 
	 * The min size possible its 1x1
	 * 
	 * @param width
	 * @param height
	 */
	public void setSize(int width, int height) {
		Widget w = getResizeDelegate();

		// TODO max and min limits
		if (width < 1) {
			width = 1;
		}
		if (height < 1) {
			height = 1;
		}

		w.setSize(width + "px", height + "px");
	}

	@Override
	public void setWidget(Widget widget) {
		p = (AbsolutePanel) widget.getParent();
		grid.setWidget(1, 1, widget);
	}

	public void setWidgetHeight(int height) {
		Widget w = getResizeDelegate();

		if (height < 1) {
			height = 1;
		}

		w.setWidth(height + "px");
	}

	public void setWidgetWidth(int width) {
		Widget w = getResizeDelegate();

		if (width < 1) {
			width = 1;
		}

		w.setWidth(width + "px");
	}

	/**
	 * Show or hide a specific border
	 */
	public void showBorder(ResizeBorders border, boolean b) {
		String value = (b) ? "visible" : "hidden";

		int index = border.ordinal();
		cellFormatter.getElement(index % 3, index / 3).getStyle().setProperty(
				"visibility", value);
	}

	/**
	 * Enable resize borders
	 */
	public void showBorders() {
		showBorders(true);
	}

	/**
	 * Sets the visibility of the borders
	 * 
	 * @param b
	 */
	public void showBorders(boolean b) {

		String value = (b) ? "visible" : "hidden";

		for (int x = 0; x < 3; x++) {
			for (int y = 0; y < 3; y++) {
				if (!(x == 1 && y == 1)) {
					cellFormatter.getElement(y, x).getStyle().setProperty(
							"visibility", value);
				}
			}
		}
	}

	/**
	 * Toogle borders
	 */
	public void toogleBorders() {
		bordersVisible = !bordersVisible;
		showBorders(bordersVisible);
	}
}
